// $Id: loggerdownloadspeed.cpp 63 2004-10-22 14:33:20Z jonas $
#include "loggerdownloadspeed.h"
#include "loghandler.h"
#include "clock.h"
#include "partitionizer.h"
//#include <fstream>
//using namespace std;

#include <iostream>

/**
 * Init members and register apropriate listeners at the log handler.
 */
LoggerDownloadSpeed::LoggerDownloadSpeed()
	: m_eos(0), m_lastTransfer(0)
{
	LogHandler::getInstance()->addLogger(this, "node_transfer");
	LogHandler::getInstance()->addLogger(this, "clock_tick");
}

/**
 * Free the allocated memory.
 */
LoggerDownloadSpeed::~LoggerDownloadSpeed()
{
/*	for (map<Node *, NodeSpeedHelper *>::iterator node=m_nodeSpeeds.begin(); node!=m_nodeSpeeds.end(); node++) {
		delete (*node).second;
	}
	m_nodeSpeeds.clear();*/

	// Clear memory for all the partition speed lists
	for (int i=0; i<(int)m_nodePartitionSpeeds.size(); i++) {
		delete m_nodePartitionSpeeds[i];
	}
}

/**
 * Create the output file and write the headers.
 */
void LoggerDownloadSpeed::eos_simulate_start(void *eos)
{
	openFile(&m_fout, "downloadspeed.csv");
	m_eos = (Eos *) eos;

	m_fout << "Time Step";

	// TODO: Find out about number of partitions and create a transfer list for each of them
	for (int i=0; i<Partitionizer::getInstance()->getPartitionCount(); i++) {
		//map<Node *, NodeSpeedHelper *> *list = new map<Node *, NodeSpeedHelper *>();
		m_nodePartitionSpeeds.push_back(new map<Node *, NodeSpeedHelper *>());
		m_fout << ", " <<
		Partitionizer::getInstance()->getPartitionName(i) << "-Average, " <<
		Partitionizer::getInstance()->getPartitionName(i) << "-Minimum, " <<
		Partitionizer::getInstance()->getPartitionName(i) << "-First Quartile, " <<
		Partitionizer::getInstance()->getPartitionName(i) << "-Median, " <<
		Partitionizer::getInstance()->getPartitionName(i) << "-Third Quartile, " <<
		Partitionizer::getInstance()->getPartitionName(i) << "-Maximum, " <<
		Partitionizer::getInstance()->getPartitionName(i) << "-Partition member count";
	}
	m_fout << endl;
}

/**
 * Close the file.
 */
void LoggerDownloadSpeed::eos_simulate_stop(void *)
{
	// Sum up everything up!
	/*for (map<Node *, NodeSpeedHelper *>::iterator node=m_nodeSpeeds.begin(); node!=m_nodeSpeeds.end(); node++) {
		fout << "nodeId: " << (*node).second->m_node << ", speed (bytes/tick): " << (*node).second->m_transferSpeed << endl;
	}*/

	m_fout.close();
}

/**
 * Pin points transfers that directly relates to a initial requestor and saves
 * them for later processing. When a transfer is complete it is measured and
 * saved for output in clock_tick.
 */
void LoggerDownloadSpeed::node_transfer(void *node, void *transfer, unsigned char status)
{
// cout << "LoggerDownloadSpeed::node_transfer(" << node << ") >> Was here..." << endl;
	// This is a newly started request transfer
	if (status == 0) {
// cout << "  request transfer." << endl;
		// Check if this is a transfer we should log (last/first in chain)
		if (!m_lastTransfer) {
			// First one, wohoo!
			m_lastTransfer = (Transfer *) transfer;
// cout << "  First ever." << endl;
		}
		else if ((((Node *) node) == m_lastTransfer->m_destination) && (((Transfer *) transfer)->m_file->m_file == m_lastTransfer->m_file->m_file)) {
			// Next node in the transfer chain AND same transfer (same file)
			m_lastTransfer = (Transfer *) transfer;
// cout << "  Next in chain." << endl;
		}
		else {
			// The is a new transfer which means that the previous one was the last in the chain. Save it!
			// TODO: What about node parameter... What should it be?
			// TODO: We WILL miss one transfer if it finishes on the same time step!!!
			//m_transfers[((Transfer *) m_lastTransfer)->m_guid] = TransferHelper((Node *) ((Transfer *) m_lastTransfer)->m_destination, (Transfer *) m_lastTransfer);
			storeTransfer((Transfer *) m_lastTransfer);
/*cout << "LoggerDownloadSpeed::node_transfer(" << node << ") >> Saving transfer!" << endl;
cout << "  guid: " << ((Transfer *) m_lastTransfer)->m_guid << endl;
cout << "  file hash: " << ((Transfer *) m_lastTransfer)->m_file->m_file->m_hash << endl;
cout << "  file size: " << ((Transfer *) m_lastTransfer)->m_file->m_file->m_size << endl;
cout << "  destination: " << ((Transfer *) m_lastTransfer)->m_destination << endl;
cout << "  current ts: " << Clock::getInstance()->getTime() << endl;*/
			m_lastTransfer = (Transfer *) transfer;
		}
	}

	// TODO: INSERTS!!!!

	else if (status == 2) {
// cout << "  transfer finished. guid: " << ((Transfer *) transfer)->m_guid << endl;
		// Find the saved start part of the transfer
		map<unsigned int, TransferHelper>::iterator savedTransfer = m_transfers.find(((Transfer *) transfer)->m_guid);

// cout << "| transfer list." << endl;
// for (map<unsigned int, TransferHelper>::iterator iter=m_transfers.begin(); iter!=m_transfers.end(); iter++) {
// 	cout << "  guid: " << (*iter).first << endl;
// }
// cout << "--- transfer list." << endl;

		if (savedTransfer == m_transfers.end()) {
			// This transfer was not saved, ignore.
// cout << "  not found!" << endl;
			return;
		}
// cout << "  FOUND!" << endl;

		// Add / update speed value in m_nodeSpeeds
		NodeSpeedHelper *nodeSpeedHelper;
		//map<Node *, NodeSpeedHelper *>::iterator savedNode = m_nodeSpeeds.find((Node *) ((Transfer *) transfer)->m_destination);
		Node *tmpNode = (Node *) ((Transfer *) transfer)->m_destination;
		int partition = Partitionizer::getInstance()->getPartition(tmpNode);
		map<Node *, NodeSpeedHelper *> *nodeSpeeds = m_nodePartitionSpeeds[partition];
		map<Node *, NodeSpeedHelper *>::iterator savedNode = nodeSpeeds->find((Node *) ((Transfer *) transfer)->m_destination);
		//if (savedNode == m_nodeSpeeds.end()) {
		if (savedNode == nodeSpeeds->end()) {
			// No previos entry about this node. Create one.
			//nodeSpeedHelper = new NodeSpeedHelper((Node *) node);
			nodeSpeedHelper = new NodeSpeedHelper((Node *) ((Transfer *) transfer)->m_destination);
			//m_nodeSpeeds[(Node *) node] = nodeSpeedHelper;
			//m_nodeSpeeds[(Node *) ((Transfer *) transfer)->m_destination] = nodeSpeedHelper;
			(*nodeSpeeds)[(Node *) ((Transfer *) transfer)->m_destination] = nodeSpeedHelper;
		}
		else {
			nodeSpeedHelper = (*savedNode).second;
		}
		int deltaTime = (Clock::getInstance()->getTime() - (*savedTransfer).second.m_timeStart);
		if (!deltaTime) {	// DEBUG
			deltaTime = 1;
		}

		// Calculate average speed on all transfers
		//int speed = (*savedTransfer).second.m_transfer->m_progress / deltaTime;

		// Calc speed on this transfer and avarage it with the previous samples.

/*Node *assertNode = ((Node *) (*savedTransfer).second.m_transfer->m_destination);
assert(assertNode);
assert(assertNode->getStoredFile((File *) (*savedTransfer).second.m_transfer->m_file->m_file));*/

		int speed = ((Node *) (*savedTransfer).second.m_transfer->m_destination)->getStoredFile((File *) (*savedTransfer).second.m_transfer->m_file->m_file)->m_size / deltaTime;
		//assert(speed);
		//assert(((Node *) (*savedTransfer).second.m_transfer->m_destination)->getStoredFile((File *) (*savedTransfer).second.m_transfer->m_file->m_file)->m_size);
		// TODO: Alert! There are Zero byte files! These (and lots of other small (or ...) files decrease stats)
		nodeSpeedHelper->addSample(speed);
// cout << "  time steps = " << deltaTime << endl;
// cout << "  file size = " << ((Transfer *) transfer)->m_file->m_file->m_size << endl;

		/*if (!nodeSpeedHelper->m_transferSpeed) {
			// If this is the first sample, we simply set the average speed to that speed
			nodeSpeedHelper->m_transferSpeed = speed;
		}
		else {
			nodeSpeedHelper->m_transferSpeed = (nodeSpeedHelper->m_transferSpeed + speed) / 2;	/// @todo Not correct algorithm used here!
		}*/

		// Remove entry from m_transfers
		m_transfers.erase(savedTransfer);
	}
}

/**
 * Gathers the statistics for each tick
 */
// TODO: Seems like some values can be measured to 0. This is NOT possible!
void LoggerDownloadSpeed::clock_tick(unsigned int time)
{
	// TODO: Add last transfer
	if (m_lastTransfer) {
		//m_transfers[((Transfer *) m_lastTransfer)->m_guid] = TransferHelper((Node *) ((Transfer *) m_lastTransfer)->m_destination, (Transfer *) m_lastTransfer);
		storeTransfer((Transfer *) m_lastTransfer);
/*cout << "LoggerDownloadSpeed::clock_tick(" << "?" << ") >> Saving transfer!" << endl;
cout << "  guid: " << ((Transfer *) m_lastTransfer)->m_guid << endl;
cout << "  file hash: " << ((Transfer *) m_lastTransfer)->m_file->m_file->m_hash << endl;
cout << "  file size: " << ((Transfer *) m_lastTransfer)->m_file->m_file->m_size << endl;
cout << "  destination: " << ((Transfer *) m_lastTransfer)->m_destination << endl;
cout << "  current ts: " << Clock::getInstance()->getTime() << endl;*/
		m_lastTransfer = 0;
	}

	// Write time step
	m_fout << time;

	// TODO: Simply print all NodeSpeedHelper
	// TODO: For all partitions
		// TODO: Build list with ints (all speeds) and sum them up
// cout << "LoggerDownloadSpeed::clock_tick(" << time << ") >> summing..." << endl;

	//for (map<Node *, NodeSpeedHelper *>::iterator speed=m_nodeSpeeds.begin(); speed!=m_nodeSpeeds.end(); speed++) {
	map<Node *, NodeSpeedHelper *> *nodeSpeeds;
	for (int i=0; i<(int) m_nodePartitionSpeeds.size(); i++) {
		nodeSpeeds = m_nodePartitionSpeeds[i];
		vector<int> speeds;
		int sumSpeeds = 0;

		for (map<Node *, NodeSpeedHelper *>::iterator speed=nodeSpeeds->begin(); speed!=nodeSpeeds->end(); speed++) {
			speeds.push_back((*speed).second->m_transferSpeed);
			sumSpeeds += (*speed).second->m_transferSpeed;
// cout << "  speed = " << (*speed).second->m_transferSpeed << endl;
		}
// cout << "  sum = " << sumSpeeds << endl;nodeSpeedHelper
// cout << "  # = " << speeds.size() << endl;
		writeCountStats(m_fout, speeds, sumSpeeds);
	}

	// Write end
	m_fout << endl;
}

/**
 * Remember a transfer so that we may pair it with its next message (which is
 * when the transfer is complete)
 */
void LoggerDownloadSpeed::storeTransfer(Transfer *transfer)
{
	// TODO: Add to correct partition list?
	m_transfers[transfer->m_guid] = TransferHelper((Node *) transfer->m_destination, transfer);
// cout << "LoggerDownloadSpeed::storeTransfer() >> This is the first transfer in a chain!" << endl;
}


/**
 * Init all members to 0.
 */
LoggerDownloadSpeed::TransferHelper::TransferHelper()
	: m_node(0), m_transfer(0), m_timeStart(0)
{
}

/**
 * Init members and set start time to now.
 * @todo Doc all params!
 */
LoggerDownloadSpeed::TransferHelper::TransferHelper(Node *node, Transfer *transfer)
	: m_node(node), m_transfer(transfer)
{
	m_timeStart = Clock::getInstance()->getTime();
}

/**
 * @todo Describe this...
 */
LoggerDownloadSpeed::NodeSpeedHelper::NodeSpeedHelper(Node *node)
	: m_node(node), m_transferSpeed(0), m_sampleCount(0)
{
}

/**
 * @todo Describe this...
 */
void LoggerDownloadSpeed::NodeSpeedHelper::addSample(int speed)
{
/*cout << "LoggerDownloadSpeed::NodeSpeedHelper::addSample() >> Check:" << endl;
cout << "  m_transferSpeed = " << m_transferSpeed << endl;
cout << "  m_sampleCount = " << m_sampleCount << endl;
cout << "  speed = " << speed << endl;
cout << " counting..." << endl;*/
	m_transferSpeed = (m_transferSpeed*m_sampleCount + speed) / (m_sampleCount++ + 1);
/*cout << "  m_sampleCount = " << m_sampleCount << endl;
cout << "  m_transferSpeed = " << m_transferSpeed << endl;*/
}
